/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package simplemetrosystem.control;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Observable;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import simplemetrosystem.model.Element.Interface.MetroElement;
import simplemetrosystem.model.Element.MetroEdge;
import simplemetrosystem.model.Element.MetroLine;
import simplemetrosystem.model.Element.MetroStation;
import simplemetrosystem.model.Element.MetroString;
import simplemetrosystem.model.charlanguage.NameMap;

/**
 *
 * @author Eric Qian
 */
public class ElementManager extends Observable {

    /**
     * 
     * @return
     */
    public static ElementManager getInstance() {
        return elementManager;
    }

    /**
     * 
     * @param path
     * @return
     */
    public static ElementManager newInstance(String path) {
        elementManager = new ElementManager();
        elementManager.setPath(path);
        if (path == null) {
            elementManager.initLine();
        }
        return elementManager;
    }

    /**
     * 
     */
    private ElementManager() {

        width = ConfigureManager.getInstance().getWidth();
        height = ConfigureManager.getInstance().getHeight();

        lines = new ArrayList<MetroLine>();
        stations = new ArrayList<MetroStation>();
        edges = new ArrayList<MetroEdge>();
        strings = new ArrayList<MetroString>();

        language = NameMap.getInstance().getDisployLanguage();

        try {
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 
     */
    private void initLine() {
        addLine(BUS_NAME, BUS_CNAME, MetroLine.STATUS_BUS, BUS_COLOR);
        addLine(FOOT_NAME, FOOT_CNAME, MetroLine.STATUS_FOOT, FOOT_COLOR);
    }

    /**
     * 
     */
    private void ElemChanges() {
        setChanged();
        notifyObservers();
    }

    /**
     * 
     * @return
     */
    public String getPath() {
        return path;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    /**
     * 
     * @param path
     */
    public void setPath(String path) {
        this.path = path;
        ElemChanges();
    }

    /**
     * 
     * @return
     */
    public int getLinesSize() {
        return lines.size();
    }

    /**
     * 
     * @return
     */
    public int getStationsSize() {
        return stations.size();
    }

    /**
     * 
     * @return
     */
    public int getEdgesSize() {
        return edges.size();
    }

    /**
     * 
     * @param i
     * @return
     */
    public MetroLine getLineAt(int i) {
        return lines.get(i);
    }

    /**
     * 
     * @param i
     * @return
     */
    public MetroStation getStationAt(int i) {
        return stations.get(i);
    }

    /**
     * 
     * @param i
     * @return
     */
    public MetroEdge getEdgeAt(int i) {
        return edges.get(i);
    }

    /**
     * 
     * @param line
     * @return
     */
    public int getIndexOfLine(MetroLine line) {
        if (lines.contains(line)) {
            return lines.indexOf(line);
        }
        return -1;
    }

    /**
     * 
     * @param i
     * @return
     */
    public MetroString getStringAt(int i) {
        return strings.get(i);
    }

    /**
     * 
     * @return
     */
    public int getStringSize() {
        return strings.size();
    }

    /**
     * 
     * @param lineName
     * @param lineCName
     * @param status 
     * @param color
     * @return 
     */
    public boolean addLine(String lineName, String lineCName, String status, Color color) {
        if (findLine(lineName) == null && lineName != null && lineCName != null && status != null && color != null) {
            lines.add(new MetroLine(lineName, lineCName, status, color, language));
            Collections.sort(lines);
            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param line
     * @return 
     */
    public boolean addLine(MetroLine line) {
        if (!lines.contains(line)) {
            lines.add(line);
            line.eUpdate();
            Collections.sort(lines);
            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param stationName
     * @param stationCName
     * @param logX
     * @param logY
     * @param natX
     * @param natY
     * @return 
     */
    public boolean addStation(
            String stationName, String stationCName, int logX, int logY, double natX, double natY) {
        if (stationName != null && logX >= 0 && logY >= 0 && natX >= 0 && natY >= 0) {
            MetroStation station = new MetroStation(stationName, stationCName, logX, logY, natX, natY, language);
            stations.add(station);
            Collections.sort(stations);

            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param station
     * @return 
     */
    public boolean addStation(MetroStation station) {
        if (!stations.contains(station)) {
            stations.add(station);
            station.eUpdate();
            Collections.sort(stations);
            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param stationA
     * @param stationB
     * @param line
     * @param serialNumber 
     * @param distance
     * @param time
     * @return 
     */
    public boolean addEdge(String stationA, String stationB, String line,
            int serialNumber, double distance, double time) {
        if (stationA != null && stationB != null && distance >= 0 && time >= 0) {
            MetroStation msA = findStation(stationA);
            MetroStation msB = findStation(stationB);
            MetroLine ml = findLine(line);
            if (msA != null && msB != null) {
                MetroEdge me = new MetroEdge(msA, msB, ml, serialNumber, distance, time, language);
                edges.add(me);
                this.edgeNotify(me);
                Collections.sort(edges);

                ElemChanges();
                return true;
            }
        }
        return false;
    }

    /**
     * 
     * @param edge
     * @return
     */
    public boolean addEdge(MetroEdge edge) {
        if (!edges.contains(edge)) {
            edges.add(edge);
            this.edgeNotify(edge);
            Collections.sort(edges);
            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param text
     * @param element
     * @param logX
     * @param logY
     * @return
     */
    public MetroString addString(String text, String element, int logX, int logY) {
        if (text != null && logX >= 0 && logY >= 0) {
            MetroString string = new MetroString(
                    text, findElement(element), logX, logY);
            strings.add(string);
            ElemChanges();
            return string;
        }
        return null;
    }

    /**
     * 
     * @param string
     * @return
     */
    public boolean addString(MetroString string) {
        if (!strings.contains(string)) {
            strings.add(string);
            ElemChanges();
            return true;
        }
        return false;
    }

    /**
     * 
     * @param line
     */
    public void removeLine(MetroLine line) {
        ArrayList<MetroEdge> edgesOfLine = getEdgesOfLine(line);
        for (int i = 0; i < edgesOfLine.size(); i++) {
            removeEdge(edgesOfLine.get(i));
        }

        lines.remove(line);
        ElemChanges();
    }

    /**
     * 
     * @param name
     */
    public void removeLine(String name) {
        MetroLine line = findLine(name);
        if (line != null) {
            this.removeLine(line);
        }
        ElemChanges();
    }

    /**
     * 
     */
    public void removeAll() {
        lines = null;
        stations = null;
        edges = null;
        lines = new ArrayList<MetroLine>();
        stations = new ArrayList<MetroStation>();
        edges = new ArrayList<MetroEdge>();
        ElemChanges();
    }

    /**
     * 
     * @param station
     */
    public void removeStation(MetroStation station) {
        ArrayList<MetroEdge> edgesOfStation = getEdgesOfStation(station);
        for (int i = 0; i < edgesOfStation.size(); i++) {
            removeEdge(edgesOfStation.get(i));
        }

        stations.remove(station);
        ElemChanges();
    }

    /**
     * 
     * @param name
     */
    public void removeStation(String name) {
        MetroStation station = findStation(name);
        if (station != null) {
            removeStation(station);
        }
        ElemChanges();
    }

    /**
     * 
     * @param edge
     */
    public void removeEdge(MetroEdge edge) {
        edges.remove(edge);
        edgeNotify(edge);
        ElemChanges();
    }

    /**
     * 
     * @param string
     */
    public void removeString(MetroString string) {
        strings.remove(string);
        ElemChanges();
    }

    /**
     * 
     * @param line
     * @param name
     * @param cName
     * @param Status
     * @param color
     */
    public void editLine(
            MetroLine line, String name, String cName, String Status, Color color) {
        line.setName(name);
        line.setChineseName(cName);
        line.setStatus(Status);
        line.setColor(color);
        ElemChanges();
    }

    /**
     * 
     * @param station
     * @param name
     * @param cName
     * @param logX
     * @param logY
     * @param natX
     * @param natY 
     */
    public void editStation(MetroStation station, String name,
            String cName, int logX, int logY, double natX, double natY) {
        station.setName(name);
        station.setChineseName(cName);
        station.setLogX(logX);
        station.setLogY(logY);
        station.setNatX(natX);
        station.setNatY(natY);
        ElemChanges();
    }

    /**
     * 
     * @param edge
     * @param line
     * @param serialNumber
     * @param distance
     * @param time
     */
    public void editEdge(MetroEdge edge, MetroLine line,
            int serialNumber, double distance, double time) {
        MetroLine aLine = edge.getLine();

        edge.setLine(line);
        edge.setSerialNumber(serialNumber);
        edge.setDistance(distance);
        edge.setTime(time);

        aLine.eUpdate();
        edgeNotify(edge);

        ElemChanges();
    }

    /**
     * 
     * @param string
     * @param text
     * @param logX
     * @param logY
     */
    public void editString(MetroString string, String text, int logX, int logY) {
        string.setString(text);
        string.setLogX(logX);
        string.setLogY(logY);
        ElemChanges();
    }

    /**
     * 
     * @param line
     * @return
     */
    public ArrayList<MetroStation> getStationsOfLine(MetroLine line) {
        ArrayList<MetroStation> stationsOfLine = new ArrayList<MetroStation>();
        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getLine() == line) {
                if (!stationsOfLine.contains(edges.get(i).getStationA())) {
                    stationsOfLine.add(edges.get(i).getStationA());
                }
                if (!stationsOfLine.contains(edges.get(i).getStationB())) {
                    stationsOfLine.add(edges.get(i).getStationB());
                }
            }
        }
        return stationsOfLine;
    }

    /**
     * 
     * @param line
     * @return
     */
    public ArrayList<MetroEdge> getEdgesOfLine(MetroLine line) {
        ArrayList<MetroEdge> edgesOfLine = new ArrayList<MetroEdge>();
        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getLine() == line) {
                edgesOfLine.add(edges.get(i));
            }
        }
        return edgesOfLine;
    }

    /**
     * 
     * @param station
     * @return
     */
    public ArrayList<MetroLine> getLinesOfStation(MetroStation station) {
        ArrayList<MetroLine> linesOfStation = new ArrayList<MetroLine>();
        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getStationA() == station ||
                    edges.get(i).getStationB() == station) {
                if (!linesOfStation.contains(edges.get(i).getLine())) {
                    linesOfStation.add(edges.get(i).getLine());
                }
            }
        }
        return linesOfStation;
    }

    /**
     * 
     * @param station
     * @return
     */
    public ArrayList<MetroEdge> getEdgesOfStation(MetroStation station) {
        ArrayList<MetroEdge> edgesOfStation = new ArrayList<MetroEdge>();
        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getStationA() == station ||
                    edges.get(i).getStationB() == station) {
                edgesOfStation.add(edges.get(i));
            }
        }
        return edgesOfStation;
    }

    /**
     * 
     * @param edge
     * @return
     */
    public ArrayList<MetroEdge> getSimilarEdges(MetroEdge edge) {
        ArrayList<MetroEdge> similarEdges = new ArrayList<MetroEdge>();
        for (int i = 0; i < edges.size(); i++) {
            if ((edges.get(i).getStationA() == edge.getStationA() &&
                    edges.get(i).getStationB() == edge.getStationB()) ||
                    (edges.get(i).getStationA() == edge.getStationB() &&
                    edges.get(i).getStationB() == edge.getStationA())) {
                similarEdges.add(edges.get(i));
            }
        }
        return similarEdges;
    }

    /**
     * 
     */
    public void eNotifyAll() {
        for (int i = 0; i < lines.size(); i++) {
            lines.get(i).eUpdate();
        }
        for (int i = 0; i < stations.size(); i++) {
            stations.get(i).eUpdate();
        }
    }

    /**
     * 
     * @param edge
     */
    public void edgeNotify(MetroEdge edge) {
        edge.getStationA().eUpdate();
        edge.getStationB().eUpdate();
        edge.getLine().eUpdate();
        ArrayList<MetroEdge> edgesL = this.getEdgesOfLine(edge.getLine());
        ArrayList<MetroEdge> edgesA = this.getEdgesOfStation(edge.getStationA());
        ArrayList<MetroEdge> edgesB = this.getEdgesOfStation(edge.getStationB());
        ArrayList<MetroEdge> done = new ArrayList<MetroEdge>();
        for (int i = 0; i < edgesL.size(); i++) {
            if (!done.contains(edgesL.get(i))) {
                edgesL.get(i).eUpdate();
                done.add(edgesL.get(i));
            }
        }
        for (int i = 0; i < edgesA.size(); i++) {
            if (!done.contains(edgesA.get(i))) {
                edgesA.get(i).eUpdate();
                done.add(edgesA.get(i));
            }
        }
        for (int i = 0; i < edgesB.size(); i++) {
            if (!done.contains(edgesB.get(i))) {
                edgesB.get(i).eUpdate();
                done.add(edgesB.get(i));
            }
        }
        done = null;
    }

    /**
     * 
     * @return
     */
    public Document buildDocument() {
        Document doc = builder.newDocument();

        Element root = doc.createElement("root");
        for (int i = 0; i < lines.size(); i++) {
            root.appendChild(lines.get(i).getElement(doc));
        }
        for (int i = 0; i < stations.size(); i++) {
            root.appendChild(stations.get(i).getElement(doc));
        }
        for (int i = 0; i < edges.size(); i++) {
            root.appendChild(edges.get(i).getElement(doc));
        }

        for (int i = 0; i < strings.size(); i++) {
            root.appendChild(strings.get(i).getElement(doc));
        }

        doc.appendChild(root);
        return doc;
    }

    @Override
    public String toString() {
        String output = "<root>" + "\n";
        for (int i = 0; i < lines.size(); i++) {
            output += lines.toString();
        }
        for (int i = 0; i < stations.size(); i++) {
            output += stations.toString();
        }
        for (int i = 0; i < edges.size(); i++) {
            output += edges.toString();
        }
        output += "</root>";
        return output;
    }

    /**
     * 
     * @param lineName
     * @return
     */
    public MetroLine findLine(String lineName) {
        for (int i = 0; i < lines.size(); i++) {
            if (lines.get(i).getName().equals(lineName) || lines.get(i).getChineseName().equals(lineName)) {
                return lines.get(i);
            }
        }
        return null;
    }

    public void sortEdges() {
        Collections.sort(edges);
        ElemChanges();
    }

    public MetroElement findElement(String elementName) {
        for (int i = 0; i < stations.size(); i++) {
            if (stations.get(i).getName().equals(elementName)) {
                return stations.get(i);
            }
        }
        for (int i = 0; i < lines.size(); i++) {
            if (lines.get(i).getName().equals(elementName)) {
                return lines.get(i);
            }
        }

        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getName().equals(elementName)) {
                return edges.get(i);
            }
        }
        return null;
    }

    /**
     * 
     * @param stationName
     * @return
     */
    public MetroStation findStation(String stationName) {
        for (int i = 0; i < stations.size(); i++) {
            if (stations.get(i).getName().equals(stationName) ||
                    stations.get(i).getChineseName().equals(stationName)) {
                return stations.get(i);
            }
        }
        return null;
    }

    /**
     * 
     * @param stA
     * @param stB
     * @param lineName
     * @return
     */
    public MetroEdge findEdge(String stA, String stB, String lineName) {
        for (int i = 0; i < edges.size(); i++) {
            if (edges.get(i).getStationA().getName().equals(stA) &&
                    edges.get(i).getStationB().getName().equals(stB) &&
                    edges.get(i).getLine().getName().equals(lineName)) {
                return edges.get(i);
            }
        }
        return null;
    }
    private static ElementManager elementManager;
    private int width;
    private int height;
    private ArrayList<MetroLine> lines;
    private ArrayList<MetroStation> stations;
    private ArrayList<MetroEdge> edges;
    private ArrayList<MetroString> strings;
    private DocumentBuilder builder;
    private boolean language;
    private String path;
    public static final String FOOT_NAME = "foot";
    public static final String FOOT_CNAME = "步行线";
    public static final Color FOOT_COLOR = Color.LIGHT_GRAY;
    public static final String BUS_NAME = "bus";
    public static final String BUS_CNAME = "公交线";
    public static final Color BUS_COLOR = Color.BLACK;
}
